#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#include "log.h"

List *list_new()
{
    List *l = malloc(sizeof(List));
    memset(l, 0, sizeof(List));
    return l;
}

void list_empty(List *list)
{
    if (!list) return;
    ListNode *node = list->head;
    ListNode *del_node = NULL;
    while (node)
    {
        if (list->free)
        {
            list->free(node->data);
        }
        del_node = node;
        node = node->next;
        free(del_node);
    }
    list->head = list->tail = NULL;
    list->len = 0;
}

void list_free(List *list)
{
    if (!list) return;
    list_empty(list);
    free(list);
}

ListNode *list_get_head_node(List *list)
{
    if (!list) return NULL;
    return list->head; 
}

void *list_head(List *list)
{
    if (!list) return NULL;
    if (!list->tail) return NULL;
    return list->head->data; 
}

ListNode *list_get_tail_node(List *list)
{
    if (!list) return NULL;
    return list->tail; 
}

void *list_tail(List *list)
{
    if (!list) return NULL;
    if (!list->tail) return NULL;
    return list->tail->data; 
}

int list_len(List *list)
{
    if (!list) return 0;
    return list->len; 
}

int list_find_node_index(List *list, ListNode *node)
{
    if (!list || !node) return -1;

    int index = 0;
    ListNode *cur = list->head;
    list_for_each(cur, list)
    {
        if (cur == node)
        {
            return index;
        }
        index++;
    }
    return -1;
}

int list_find(List *list, void *data)
{
    int index = 0;
    ListNode *cur = list->head;
    bool match;
    list_for_each(cur, list)
    {
        match = list->match ? \
            list->match(cur->data, data) :\
            cur->data==data;
        if (match)
        {
            return index;
        }
        index++;
    }
    return -1;
}

void *list_get(List *list, int index)
{
    ListNode *node;
    int i = 0;
    list_for_each(node, list)
    {
        if (i == index)
        {
            return node->data;
        }
        i++;
    }
    return NULL;
}

static int alloc_node_and_apply(List *list, void *data, int (*func)(List *, ListNode *))
{
    ListNode *node = malloc(sizeof(ListNode));
    if (!node)
    {
        log_error("malloc node error");
        return -1;
    }
    node->data = data;
    return func(list, node);
}

static int insert_first_node(List *list, ListNode *node)
{
    if (!list || !node) return -1;
    if (list->len) return -1;
    list->head = node;
    list->tail = node;
    list->len++;
    return 0;
}

int list_insert_head_node(List *list, ListNode *node)
{
    if (!list || !node) return -1;
    if (!list->len)
    {
        return insert_first_node(list, node);
    }

    (list->head)->prev = node; 
    node->prev = NULL;
    node->next = list->head;
    list->head = node;
    list->len++;
    return 0;
}

int list_insert_head(List *list, void *data)
{
    if (!list) return -1;

    return alloc_node_and_apply(list, data, list_insert_head_node);
}

int list_insert_tail_node(List *list, ListNode *node)
{
    if (!list || !node) return -1;
    if (!list->len)
    {
        return insert_first_node(list, node);
    }

    (list->tail)->next = node;
    node->prev = list->tail;
    node->next = NULL;
    list->tail = node;
    list->len++;
    return 0;
}

int list_insert_tail(List *list, void *data)
{
    if (!list) return -1;
    return alloc_node_and_apply(list, data, list_insert_tail_node);
}

int list_insert_before(List *list, ListItr pos, ListNode *node)
{
    if (!list || !node)
    {
        return -1;
    }

    if(!pos)
    {
        return list_insert_tail_node(list, node);        
    }

    if(pos == list->head)
    {
        return list_insert_head_node(list, node);
    }

    node->prev = pos->prev;
    node->next = pos;
    pos->prev = node;
    list->len++;
    return 0;
}

int list_insert_before_safe(List *list, ListNode *pos, ListNode *node)
{
    if (list_find_node_index(list, pos) == -1) return -1;
    return list_insert_before(list, pos, node);
}

static ListNode *del_last_node(List *list)
{
    if (!list) return NULL;
    if (list->len != 1) return NULL;
    
    ListNode *node = list->head;
    list->head = NULL;
    list->tail = NULL;
    list->len = 0;
    return node;
}

ListNode *list_del_head_node(List *list)
{
    if (!list) return NULL;
    if (!list->len) return NULL;
    if (list->len == 1)
    {
        return del_last_node(list);
    }
    
    ListNode *node = list->head;
    list->head = node->next;
    list->len--;

    return node;
}

void *list_del_head(List *list)
{
    ListNode *node = list_del_head_node(list);
    void *data = NULL;
    if (node)
    {
        data = node->data;
        free(node);
    }
    return data;
}

ListNode *list_del_tail_node(List *list)
{
    if (!list) return NULL;
    if (!list->len) return NULL;
    if (list->len == 1)
    {
        return del_last_node(list);
    }
    
    ListNode *node = list->tail;
    list->tail = node->prev;
    list->len--;

    return node;
}

void *list_del_tail(List *list)
{
    ListNode *node = list_del_tail_node(list);
    void *data = NULL;
    if (node)
    {
        data = node->data;
        free(node);
    }
    return data;
}

ListNode *list_del_node(List *list, ListNode *node)
{
    if (!list) return NULL;
    if (!list->len) return NULL;
    if (list->head == node)
        return list_del_head_node(list);
    if (list->tail == node)
        return list_del_tail_node(list);
    
    (node->prev)->next = node->next;
    (node->next)->prev = node->prev;
    list->len--;
    return node;
}

ListNode *list_del_node_safe(List *list, ListNode *node)
{
    if (list_find_node_index(list, node) == -1) return NULL;
    return list_del_node(list, node);
}

void list_print(List *list, void (*print)(void *))
{
    if (!list || !print) return;

    ListNode *node = NULL;
    list_for_each(node, list)
    {
        print(node->data);
    }
}

int list_insert_order(List *list, ListNode *node, int (*compare)(void *, void *))
{
    if (!list || !compare) return -1;

    ListNode *p = NULL;
    list_for_each(p, list)
    {
        if (compare(p->data, node->data) > 0)
        {
            return list_insert_before(list, p, node);
        }
    }

    return list_insert_tail_node(list, node);
}